package search;

import movgen.Movegen;
import state.GameState;

import com.alonsoruibal.chess.bitboard.BishopMagic;
import com.alonsoruibal.chess.bitboard.RookMagic;

/****
 * 
 * A Static Exchange Evaluation (SEE) examines the consequence of a series of exchanges 
 * on a single square after a given move, and calculates the likely evaluation change 
 * (material) to be lost or gained, Donald Michie coined the term swap-off value. 
 * 
 * A positive static exchange indicates a "winning" move.
 *  For example, PxQ will always be a win, since the Pawn side can choose to stop the exchange after its Pawn is recaptured,
 *   and still be ahead.
 *    Some programs optimize the SEE function to only return a losing or equal/winning flag, since they only use SEE to determine
 *     if a move is worth searching and do not need the actual value.
 *      SEE is useful in move ordering, futility pruning and especially in quiescence search in conjunction with delta pruning,
 *       as well to reduce "bad" captures and checks [1] .
 */

public class SEE {
	
	// This is the main routine used by search
	public static int see(GameState state, int move, int side)  // the side that is moving
	{
		// does not work for stack of three XRay attackers/defenders, only for stack of two
		// does not work for xray bishop/queen behind attacking pawn  (:(
	
		//int from2 = Move2.fromIndex(move);
		//int to2 = Move2.toIndex(move);
		
		int to2 = ((move >> 6) & 63);
		int victimSquare = 63 - to2;
		final int[] valueMap = { 100, 100, 325,  325, 
			350, 	350,  500,
			500, 975 ,975, 9999, 9999};
		
	
		long all =  state.bitboard[GameState.ALLPIECES];
	
		long[] queens= new long[2];
		long[] bishops= new long[2];
		long[] rooks= new long[2];
		long[] knights= new long[2];
		
		for (int i=0; i < 2; i++) {
			queens[i] = state.bitboard[GameState.WQ+i];
			rooks[i] = state.bitboard[GameState.WR+i];
			bishops[i] = state.bitboard[GameState.WB+i];
			knights[i] = state.bitboard[GameState.WN+i];
		}
	
		//long pawns = state.bitboard[GameState.WR+side];
		
		final long[] whiteRookBlockers= { rooks[0], queens[0]};
		final long[] blackRookBlockers= { rooks[1], queens[1]};
		final long[] whiteBishopBlockers= { bishops[0], queens[0]};
		final long[] blackBishopBlockers= { bishops[1], queens[1]};
		
		final long[][] rookBlockers = { whiteRookBlockers, blackRookBlockers};
		final long[][] bishopBlockers = { whiteBishopBlockers, blackBishopBlockers};
		
		
		int[][] attackers;    // colors, pieces
		
	
		//get regular attackers
		int[]ptr = { -1,-1};
		attackers = getRegularAttackers(state, victimSquare, all, 
				queens, rooks, bishops, knights, ptr);
		
		// move the original attackers to the top of the stack
		// 'side' is the attacking side
		
		int from2 =  (move & 63); 
	    int attacker = state.board[from2];    //the initial attacker
		for (int i=0; i <= ptr[side]; i++) {
			if (attackers[side][i] == attacker) {
			
				for (int j = i; j < ptr[side]; j++) {
					attackers[side][j]=attackers[side][j+1];
				}
				attackers[side][ptr[side]] = attacker;
				break;
			}
		}
		//put the xRayAttackers into the stack, but behind their associated regular attackers
		int[][] diagXRayAttacker = new int[2][2];   //white, black and attacker, blocker
		int[][] vertHorizXRayAttacker= new int[2][2];
		
		for (int i=0; i < 2; i++) {
			diagXRayAttacker[i] =         SEE.getXRayAttackerBishop(state, all, victimSquare, bishopBlockers[i], i);
			
			if (diagXRayAttacker[i] != null) {
				
				int attacker2= diagXRayAttacker[i][0];
				int blocker2= diagXRayAttacker[i][1];
				insertXRayAttacker(side, attackers,ptr, attacker2, blocker2);
			}
			vertHorizXRayAttacker[i] =    SEE.getXRayAttackerRook(state, all, victimSquare, rookBlockers[i], i);
			if (vertHorizXRayAttacker[i] != null) {
				int attacker2= vertHorizXRayAttacker[i][0];
				int blocker2= vertHorizXRayAttacker[i][1];
				insertXRayAttacker(side, attackers,ptr, attacker2, blocker2);
			}
		}
		//finally, evaluate
		  
		int victim = state.board[to2];      // the initial victim
	    if (victim==-1)   return 0;
		
	    if (ptr[side]==-1) return 0;
	
	    int value=0;
	    int origSide=side;
	   
		while ( ptr[side] >= 0 ) {
			if (victim == -1)  break;
		    int piece= attackers[side][ptr[side]];
		    ptr[side]--;
			int value2 = valueMap[victim];
			
			if (side == 0) {
				value += value2;
			}
			else {
				value -= value2;
			}
			victim=piece;
			side=other(side);
		}
		
		int retval=value;
		if (origSide == 1) retval=-value;
		
		if (value == 0 ) return 1;
		
	    return Math.max(   0, retval );	 
		 // System.out.println("value= " + value);
		 
		//Make sure not to use for enpassant capture !!
		
	}
	
	private static void insertXRayAttacker(int side, int[][] attackers, int[] ptr,
			 int xrayAttacker, int blocker) {
		
			for (int j=0; j <= ptr[side]; j++) {      // find the blocker on the attacker stack
				if (attackers[side][j] == blocker) {
					ptr[side]++;
					for (int k = ptr[side];  k > j; k--) {
						attackers[side][k]=attackers[side][k-1];
					}
					attackers[side][j] = xrayAttacker;
					break;
				}
			}
	}
	
	private static int[][] getRegularAttackers(GameState state, int to, long allPieces,
			long queen[], long rook[], long bishop[], long knight[], int[] ptr){
		
		int[][] attackers= new int[2][16];    // colors, pieces
		for (int i=0; i < 2; i++ ) {
			for (int j=0; j < 16; j++ ) {
				attackers[i][j]=-1;
			}
		}
		long kingAttacks =  Movegen.kingMoveArray[to];
		long bishopAttacks= BishopMagic.getBishopAttacks(to, allPieces);
		long rookAttacks = RookMagic.getRookAttacks(to,allPieces);
		long knightAttacks = Movegen.knightMoveArray[to];
		
		for (int side=0; side < 2; side++) {
			if ( (kingAttacks & state.bitboard[GameState.WK+side]) != 0 ) 
				attackers[side][ ++ptr[side]]= GameState.WK+side;
			
			if ((bishopAttacks & queen[side])!= 0) 
				attackers[side] [++ptr[side]]= GameState.WQ+side;
			else {
				if ((rookAttacks & queen[side] )!= 0) 
					attackers[side] [++ptr[side]]= GameState.WQ+side;
			}
			
			if ((rookAttacks & rook[side] )!= 0) 
				attackers[side][ ++ptr[side]]= GameState.WR+side;
			
			if ((bishopAttacks & bishop[side] )!= 0) 
				attackers[side][ ++ptr[side]]= GameState.WB+side;
			
			if ( (knightAttacks & knight[side]) != 0 ) 
			   attackers[side][ ++ptr[side]]= GameState.WN+side;
		}
		
        long piece = 1L << to;
		
	    long c = ((piece & ~Movegen.fileA) >>> 7 );
	    if ( (c  & state.bitboard[GameState.WP]) != 0 ) 
	    	attackers[0][++ptr[0]]= GameState.WP;
	    
		long d = ((piece & ~Movegen.fileH) >>> 9 );
		if ( (d  & state.bitboard[GameState.WP]) != 0 ) 
			attackers[0][++ptr[0]]= GameState.WP;
		
		c = ((piece & ~ Movegen.fileH ) << 7);   //capture
		if (( c & state.bitboard[GameState.BP]) != 0 ) 
			attackers[1][++ptr[1]]= GameState.BP;
		
		d = ((piece  & ~Movegen.fileA)  << 9 );  //capture
		if (( d & state.bitboard[GameState.BP]) != 0 ) 
			attackers[1][++ptr[1]]= GameState.BP;
		
		return attackers;
	}
	
	
	
	public static int evaluate2(int side, int victim, int[][] attackers,  int[] ptr) {
		final int[] valueMap = { 100, 100, 325,  325, 
				350, 	350,  500,
				500, 975 ,975, 9999, 9999};
		    if (victim==-1)   return 0;
		
		    if (ptr[side]==-1) return 0;
		
		    int value=0;
		    int origSide=side;
		   
			while ( ptr[side] >= 0 ) {
				if (victim == -1)  break;
			    int piece= attackers[side][ptr[side]];
			    ptr[side]--;
				int value2 = valueMap[victim];
				
				if (side == 0) {
					value += value2;
				}
				else {
					value -= value2;
				}
				victim=piece;
				side=other(side);
			}
			
			int retval=value;
			if (origSide == 1) retval=-value;
			
			if (value == 0 ) return 1;
			
		    return Math.max(   0, retval );	 
		}
	

	private static long  xrayRookAttacks(long occ, long blockers, int rookSq) {
		   long attacks = RookMagic.getRookAttacks(rookSq, occ);
		   blockers &= attacks;
		   return attacks ^ RookMagic.getRookAttacks( rookSq, occ ^ blockers);
		}
	
	private static long  xrayBishopAttacks(long occ, long blockers, int bishopSq) {
		   long attacks = BishopMagic.getBishopAttacks(bishopSq, occ );
		   blockers &= attacks;
		   return attacks ^ BishopMagic.getBishopAttacks(bishopSq, occ ^ blockers );
		}
	
	public static int[] getXRayAttackerRook(GameState gs, long all, int i, long[] blockers, int attackingColor) {
		//blockers should be rooks, queens
		for (int j=0; j < blockers.length; j++) {
			long xray = xrayRookAttacks(all ,blockers[j], i) ;
			int victimSquare=63-i;
			int victim= gs.board[victimSquare];
			if (victim == -1 || victim == attackingColor) return null;
			
			int idx = Long.numberOfTrailingZeros(xray);
			while (idx < 64) {
				int attackerSquare=63-idx;
				
				int attacker = gs.board[attackerSquare];
				
				if ( (attacker != (GameState.WR+attackingColor)) && ( attacker != (GameState.WQ+attackingColor)   )     )  {
					xray = xray & ( xray - 1 );
					idx = Long.numberOfTrailingZeros(xray);
					continue;
				}
				
			/*	System.out.println("Rook XRay : victimSquare " + victimSquare + 
						"  victim  " +  victim  + 
						" attackerSquare " + attackerSquare + 
					   "  attacker " + attacker);*/
				
				int blocker=GameState.WR+attackingColor;
				if (j==1) blocker=GameState.WQ+attackingColor;
				
				int[] retval = { attacker, blocker};
				return retval;
			}
		}
		return null;
	}
	public static int[] getXRayAttackerBishop(GameState gs, long all, int i, long[] blockers, int attackingColor) {
		//blockers should be queens, rooks
		for (int j=0; j < blockers.length; j++) {
			long xray = xrayBishopAttacks(all ,blockers[j], i) ;
			int victimSquare=63-i;
			int victim= gs.board[victimSquare];
			if (victim == -1 || victim == attackingColor) return null;
			
			int idx = Long.numberOfTrailingZeros(xray);
			while (idx < 64) {
				int attackerSquare=63-idx;
				
				int attacker = gs.board[attackerSquare];
				
				if ( (attacker != (GameState.WB+attackingColor)) && ( attacker != (GameState.WQ+attackingColor)   )     )  {
					xray = xray & ( xray - 1 );
					idx = Long.numberOfTrailingZeros(xray);
					continue;
				}
				
			/*	System.out.println("Bishop XRay : victimSquare " + victimSquare + 
						"  victim  " +  victim  + 
						" attackerSquare " + attackerSquare + 
					   "  attacker " + attacker);*/
				int blocker;
				
				if (j==0) blocker=GameState.WB+attackingColor;
				else
					 blocker=GameState.WQ+attackingColor;
				int[] retval = { attacker, blocker};
				return retval;
			}
		}
		return null;
	}
	

	private static int other(int side) {
		if (side==GameState.WHITE) return GameState.BLACK; else return GameState.WHITE;
	}
	
}
